//****************************************************
// Porman Project 2
// Filename: CTextureObject.cpp
// Date: 2009/03/01
// Author: KengYu Lin
// Modify:
//****************************************************
#include "CRenderDevice.h"
#include "CTextureObject.h"
#include "CAssert.h"

namespace Porman
{

namespace GL
{
//--------------------------------
// class CSamplerState
//--------------------------------
CSamplerState::CSamplerState( void )
{
    reset();
}

CSamplerState::CSamplerState( const CSamplerState& l )
{
    (*this) = l;
}

void CSamplerState::reset( void )
{
    m_eCoordWrap_s  = E_WRAP_REPEAT;
    m_eCoordWrap_r  = E_WRAP_REPEAT;
    m_eCoordWrap_t  = E_WRAP_REPEAT;
    m_eMagFilter    = E_MAG_LINEAR;
    m_eMinFilter    = E_MIN_LINEAR;
    m_eMipMapFilter = E_MIPMAP_NON;

    m_fBorderColor[0] = m_fBorderColor[1] = m_fBorderColor[2] = m_fBorderColor[3] = 0.0f;
    m_fAnisotropicVal = 1.0f;
}

CSamplerState& CSamplerState::operator= (const CSamplerState& l)
{
    this->m_eCoordWrap_r  = l.m_eCoordWrap_r;
    this->m_eCoordWrap_s  = l.m_eCoordWrap_s;
    this->m_eCoordWrap_t  = l.m_eCoordWrap_t;
    this->m_eMagFilter    = l.m_eMagFilter;
    this->m_eMinFilter    = l.m_eMinFilter;
    this->m_eMipMapFilter = l.m_eMipMapFilter;

    this->m_fBorderColor[0] = l.m_fBorderColor[0];
    this->m_fBorderColor[1] = l.m_fBorderColor[1];
    this->m_fBorderColor[2] = l.m_fBorderColor[2];
    this->m_fBorderColor[3] = l.m_fBorderColor[3];

    this->m_fAnisotropicVal = l.m_fAnisotropicVal;

    return (*this);
}

bool CSamplerState::operator==( const CSamplerState& l )
{
    return !((*this) != l); 
}

bool CSamplerState::operator!=( const CSamplerState& l )
{
    if( m_eCoordWrap_r != l.m_eCoordWrap_r ||
        m_eCoordWrap_s != l.m_eCoordWrap_s ||
        m_eCoordWrap_t != l.m_eCoordWrap_t ||
        m_eMagFilter != l.m_eMagFilter ||
        m_eMinFilter != l.m_eMinFilter ||
        m_eMipMapFilter != l.m_eMipMapFilter ||
        m_fBorderColor[0] != l.m_fBorderColor[0] ||
        m_fBorderColor[1] != l.m_fBorderColor[1] ||
        m_fBorderColor[2] != l.m_fBorderColor[2] ||
        m_fBorderColor[3] != l.m_fBorderColor[3] ||
        m_fAnisotropicVal != l.m_fAnisotropicVal )
        return true;
    else
        return false;
}

//--------------------------------
// class CTextureBaseObject
//--------------------------------
CTextureBaseObject::CTextureBaseObject( CRenderDevice* pDevice, ETexType enumType ) :
CBaseObject(pDevice),
m_eType(enumType),
m_Sampler(),
m_iNumOfMipmap(0)
{
    glGenTextures( 1, &m_uiObjID );
    glGenBuffers( 1, &m_uiBufID );
    if( m_uiObjID != 0 && m_uiBufID != 0 )
    {
        setName("Non");
        m_iNumOfTextureObj++;
        _setSamplerState( m_Sampler, false );
    }
    else
        throw 0;
}

CTextureBaseObject::~CTextureBaseObject( void )
{
    glDeleteTextures( 1, &m_uiObjID );
    glDeleteBuffers( 1, &m_uiBufID );
    m_iNumOfTextureObj--;
}

ERESULT 
CTextureBaseObject::_setSamplerState( const CSamplerState& Sampler, bool bCheck )
{
    if( bCheck && m_Sampler == Sampler )
        return E_SUCCESS;

    THREAD_CHECKING(m_pDeviceRef->getCurThreadID(), E_THREAD_ERROR);

    m_Sampler = Sampler;

    //--------------------------
    // Binding:
    //--------------------------
    glBindTexture( m_eType, m_uiObjID );

    //--------------------------
    // Setup parameters:
    //--------------------------
    switch( m_eType )
    {
    case E_TEXTURE_2D:
    case E_TEXTURE_CUBE_MAP:
        {
            glTexParameteri( m_eType, CSamplerState::E_WRAP_S, m_Sampler.m_eCoordWrap_s );
            glTexParameteri( m_eType, CSamplerState::E_WRAP_T, m_Sampler.m_eCoordWrap_t );
        }
        break;
    case E_TEXTURE_3D:
        {
            glTexParameteri( m_eType, CSamplerState::E_WRAP_S, m_Sampler.m_eCoordWrap_s );
            glTexParameteri( m_eType, CSamplerState::E_WRAP_T, m_Sampler.m_eCoordWrap_t );
            glTexParameteri( m_eType, CSamplerState::E_WRAP_R, m_Sampler.m_eCoordWrap_r );
        }
        break;
    }

    //--------------------------
    // Setup filiter:
    //--------------------------
    glTexParameteri( m_eType, CSamplerState::E_MAG_FILTER, m_Sampler.m_eMagFilter );
    switch( m_Sampler.m_eMinFilter )
    {
    case E_MIN_NEAREST:
        {
            switch( m_Sampler.m_eMipMapFilter )
            {
            case E_MIPMAP_NON:
                {
                    glTexParameteri( m_eType, CSamplerState::E_MIN_FILTER, GL_NEAREST );
                    glTexParameterf( m_eType, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f );
                }
                break;
            case E_MIPMAP_NEAREST:
                {
                    glTexParameteri( m_eType, CSamplerState::E_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST );
                    glTexParameterf( m_eType, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f );
                }
                break;
            case E_MIPMAP_LINEAR:
                {
                    glTexParameteri( m_eType, CSamplerState::E_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR );
                    glTexParameterf( m_eType, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f );
                }
                break;
            }
        }
        break;
    case E_MIN_LINEAR:
        {
            switch( m_Sampler.m_eMipMapFilter )
            {
            case E_MIPMAP_NON:
                {
                    glTexParameteri( m_eType, CSamplerState::E_MIN_FILTER, GL_LINEAR );
                    glTexParameterf( m_eType, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f );
                }
                break;
            case E_MIPMAP_NEAREST:
                {
                    glTexParameteri( m_eType, CSamplerState::E_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
                    glTexParameterf( m_eType, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f );
                }
                break;
            case E_MIPMAP_LINEAR:
                {
                    glTexParameteri( m_eType, CSamplerState::E_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
                    glTexParameterf( m_eType, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f );
                }
                break;
            }
        }
        break;
    case E_MIN_ANISOTROPIC:
        {
            glTexParameteri( m_eType, CSamplerState::E_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
            glTexParameterf( m_eType, GL_TEXTURE_MAX_ANISOTROPY_EXT, m_Sampler.m_fAnisotropicVal );
        }
        break;
    }

    glTexParameterfv( m_eType, CSamplerState::E_BORDER_COLOR, m_Sampler.m_fBorderColor );

    //--------------------------
    // UnBinding:
    //--------------------------
    glBindTexture( m_eType, 0 );
    return E_SUCCESS;
}

unsigned int 
CTextureBaseObject::_sizeof( EDataFormat enumFormat )
{
    if( enumFormat >= E_DATAFORMAT_RED_UBYTE && enumFormat <= E_DATAFORMAT_LUMINANCE_ALPHA_FLOAT )
    {
        switch( enumFormat % 7 )
        {
        case 0:
            return sizeof(unsigned char);
        case 1:
            return sizeof(char);
        case 2:
            return sizeof(unsigned short);
        case 3:
            return sizeof(short);
        case 4:
            return sizeof(unsigned int);
        case 5:
            return sizeof(int);
        case 6: 
            return sizeof(float);
        }
    }
    else if( enumFormat >= E_DATAFORMAT_RGB_UBYTE && enumFormat <= E_DATAFORMAT_BGR_FLOAT )
    {
        switch( enumFormat % 7 )
        {
        case 0:
            return sizeof(unsigned char) * 3;
        case 1:
            return sizeof(char) * 3;
        case 2:
            return sizeof(unsigned short) * 3;
        case 3:
            return sizeof(short) * 3;
        case 4:
            return sizeof(unsigned int) * 3;
        case 5:
            return sizeof(int) * 3;
        case 6: 
            return sizeof(float) * 3;
        }
    }
    else if( enumFormat >= E_DATAFORMAT_RGBA_UBYTE && enumFormat <= E_DATAFORMAT_BGRA_FLOAT )
    {
        switch( enumFormat % 7 )
        {
        case 0:
            return sizeof(unsigned char) * 4;
        case 1:
            return sizeof(char) * 4;
        case 2:
            return sizeof(unsigned short) * 4;
        case 3:
            return sizeof(short) * 4;
        case 4:
            return sizeof(unsigned int) * 4;
        case 5:
            return sizeof(int) * 4;
        case 6: 
            return sizeof(float) * 4;
        }
    }
    else if( enumFormat >= E_DATAFORMAT_RGB_UBYTE_3_3_2 && enumFormat <= E_DATAFORMAT_BGR_USHORT_5_6_5_REV )
    {
        switch( enumFormat % 4 )
        {
        case 0:
            return sizeof(unsigned char);
        case 1:
            return sizeof(unsigned char);
        case 2:
            return sizeof(unsigned short);
        case 3:
            return sizeof(unsigned short);
        }
    }
    else if( enumFormat >= E_DATAFORMAT_RGBA_USHORT_4_4_4_4 && enumFormat <= E_DATAFORMAT_BGRA_UINT_2_10_10_10_REV )
    {
        switch( enumFormat % 8 )
        {
        case 0:
            return sizeof(unsigned short);
        case 1:
            return sizeof(unsigned short);
        case 2:
            return sizeof(unsigned short);
        case 3:
            return sizeof(unsigned short);
        case 4:
            return sizeof(unsigned int);
        case 5:
            return sizeof(unsigned int);
        case 6:
            return sizeof(unsigned int);
        case 7:
            return sizeof(unsigned int);
        }
    }
    else if( enumFormat >= E_DATAFORMAT_RGB_DXT1 && enumFormat <= E_DATAFORMAT_RGBA_DXT1 )
    {
        return 8;
    }
    else if( enumFormat >= E_DATAFORMAT_RGBA_DXT3 && enumFormat <= E_DATAFORMAT_RGBA_DXT5 )
    {
        return 16;
    }

    assert(0);
    return 0;
}

unsigned int 
CTextureBaseObject::_getGLFormat( EDataFormat enumFormat )
{
    if( enumFormat >= E_DATAFORMAT_RED_UBYTE && enumFormat <= E_DATAFORMAT_RED_FLOAT )
        return GL_RED;
    else if( enumFormat >= E_DATAFORMAT_GREEN_UBYTE && enumFormat <= E_DATAFORMAT_GREEN_FLOAT )
        return GL_GREEN;
    else if( enumFormat >= E_DATAFORMAT_BLUE_UBYTE && enumFormat <= E_DATAFORMAT_BLUE_FLOAT )
        return GL_BLUE;
    else if( enumFormat >= E_DATAFORMAT_ALPHA_UBYTE && enumFormat <= E_DATAFORMAT_ALPHA_FLOAT )
        return GL_ALPHA;
    else if( enumFormat >= E_DATAFORMAT_LUMINANCE_UBYTE && enumFormat <= E_DATAFORMAT_LUMINANCE_FLOAT )
        return GL_LUMINANCE;
    else if( enumFormat >= E_DATAFORMAT_LUMINANCE_ALPHA_UBYTE && enumFormat <= E_DATAFORMAT_LUMINANCE_ALPHA_FLOAT )
        return GL_LUMINANCE_ALPHA;
    else if( enumFormat >= E_DATAFORMAT_RGB_UBYTE && enumFormat <= E_DATAFORMAT_RGB_FLOAT ||
             enumFormat >= E_DATAFORMAT_RGB_UBYTE_3_3_2 && enumFormat <= E_DATAFORMAT_RGB_USHORT_5_6_5_REV )
        return GL_RGB;
    else if( enumFormat >= E_DATAFORMAT_BGR_UBYTE && enumFormat <= E_DATAFORMAT_BGR_FLOAT ||
             enumFormat >= E_DATAFORMAT_BGR_UBYTE_3_3_2 && enumFormat <= E_DATAFORMAT_BGR_USHORT_5_6_5_REV )
        return GL_BGR;
    else if( enumFormat >= E_DATAFORMAT_RGBA_UBYTE && enumFormat <= E_DATAFORMAT_RGBA_FLOAT ||
             enumFormat >= E_DATAFORMAT_RGBA_USHORT_4_4_4_4 && enumFormat <= E_DATAFORMAT_RGBA_UINT_2_10_10_10_REV )
        return GL_RGBA;
    else if( enumFormat >= E_DATAFORMAT_BGRA_UBYTE && enumFormat <= E_DATAFORMAT_BGRA_FLOAT ||
             enumFormat >= E_DATAFORMAT_BGRA_USHORT_4_4_4_4 && enumFormat <= E_DATAFORMAT_BGRA_UINT_2_10_10_10_REV )
        return GL_BGRA;
    
    assert(0);
    return GL_RGBA;
}

unsigned int 
CTextureBaseObject::_getGLType( EDataFormat enumFormat )
{
    if( enumFormat >= E_DATAFORMAT_RED_UBYTE && enumFormat <= E_DATAFORMAT_BGRA_FLOAT )
    {
        switch( enumFormat % 7 )
        {
        case 0:
            return GL_UNSIGNED_BYTE;
        case 1:
            return GL_BYTE;
        case 2:
            return GL_UNSIGNED_SHORT;
        case 3:
            return GL_SHORT;
        case 4:
            return GL_UNSIGNED_INT;
        case 5:
            return GL_INT;
        case 6: 
            return GL_FLOAT;
        }
    }
    else if( enumFormat >= E_DATAFORMAT_RGB_UBYTE_3_3_2 && enumFormat <= E_DATAFORMAT_BGR_USHORT_5_6_5_REV )
    {
        switch( enumFormat % 4 )
        {
        case 0:
            return GL_UNSIGNED_BYTE_3_3_2;
        case 1:
            return GL_UNSIGNED_BYTE_2_3_3_REV;
        case 2:
            return GL_UNSIGNED_SHORT_5_6_5;
        case 3:
            return GL_UNSIGNED_SHORT_5_6_5_REV;
        }
    }
    else if( enumFormat >= E_DATAFORMAT_RGBA_USHORT_4_4_4_4 && enumFormat <= E_DATAFORMAT_BGRA_UINT_2_10_10_10_REV )
    {
        switch( enumFormat % 8 )
        {
        case 0:
            return GL_UNSIGNED_SHORT_4_4_4_4;
        case 1:
            return GL_UNSIGNED_SHORT_4_4_4_4_REV;
        case 2:
            return GL_UNSIGNED_SHORT_5_5_5_1;
        case 3:
            return GL_UNSIGNED_SHORT_1_5_5_5_REV;
        case 4:
            return GL_UNSIGNED_INT_8_8_8_8;
        case 5:
            return GL_UNSIGNED_INT_8_8_8_8_REV;
        case 6:
            return GL_UNSIGNED_INT_10_10_10_2;
        case 7:
            return GL_UNSIGNED_INT_2_10_10_10_REV;
        }
    }

    assert(0);
    return GL_UNSIGNED_SHORT_5_6_5;
}

//------------------------------
// class CTexture2DObject
//------------------------------
CTexture2DObject::CTexture2DObject( CRenderDevice* pDevice, STexture2DDesc& Desc ) :
CTextureBaseObject(pDevice, E_TEXTURE_2D)
{
    glBindTexture( m_eType, m_uiObjID );

    int iWidth = Desc.m_iWidth;
    int iHeight = Desc.m_iHeight;
    m_iNumOfMipmap = Desc.m_iNumMipmap;
    m_pLevelDescTable = new STexture2DLevelDesc[m_iNumOfMipmap];

    for( int i=0; i<m_iNumOfMipmap; i++ )
    {
        if( iWidth == 0 ) iWidth = 1;
        if( iHeight == 0 ) iHeight = 1;

        m_pLevelDescTable[i].m_eInterFormat = Desc.m_eInterFormat;
        m_pLevelDescTable[i].m_iWidth = iWidth;
        m_pLevelDescTable[i].m_iHeight = iHeight;
        m_pLevelDescTable[i].m_iBorder = Desc.m_iBorder;

        if( m_pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
            m_pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
            m_pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
            m_pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
        {
            glCompressedTexImage2D( m_eType, 
                                    i, 
                                    m_pLevelDescTable[i].m_eInterFormat,
                                    m_pLevelDescTable[i].m_iWidth, m_pLevelDescTable[i].m_iHeight, m_pLevelDescTable[i].m_iBorder,
                                    0,
                                    NULL );
        }
        else
        {
            glTexImage2D( m_eType, 
                          i, 
                          m_pLevelDescTable[i].m_eInterFormat, 
                          m_pLevelDescTable[i].m_iWidth, m_pLevelDescTable[i].m_iHeight, m_pLevelDescTable[i].m_iBorder,
                          _getGLFormat(E_DATAFORMAT_RGBA_BYTE), _getGLType(E_DATAFORMAT_RGBA_BYTE), 
                          NULL );
        }

        iWidth /= 2;
        iHeight /= 2;
    }

    glBindTexture( m_eType, 0 );
}

CTexture2DObject::~CTexture2DObject( void )
{
    SAFEDELETE_ARRAY( m_pLevelDescTable );
}

ERESULT 
CTexture2DObject::getLevelDesc( int iLevel, STexture2DLevelDesc& Desc )
{
    if( (iLevel < 0 && iLevel >= m_iNumOfMipmap) )
        return E_INVALID_VALUE;
    else
    {
        Desc = m_pLevelDescTable[iLevel];
        return E_SUCCESS;
    }
}

ERESULT 
CTexture2DObject::setData( int iLevel, int iXOffset, int iYOffset, int iWidth, int iHeight, EDataFormat enumDataFormat, void* pData )
{
    THREAD_CHECKING(m_pDeviceRef->getCurThreadID(), E_THREAD_ERROR);

    ERESULT result = E_INVALID_OPERATION;
    int iSizeInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iSizeInBytes = ((iWidth+3)/4) * ((iHeight+3)/4) * _sizeof(enumDataFormat);
    else
        iSizeInBytes = iWidth * iHeight * _sizeof(enumDataFormat);

    if( iSizeInBytes != 0 && iLevel >= 0 && iLevel < m_iNumOfMipmap )
    {
        glBindBuffer( GL_PIXEL_UNPACK_BUFFER, m_uiBufID );
        glBindTexture( m_eType, m_uiObjID );
        glBufferData( GL_PIXEL_UNPACK_BUFFER, iSizeInBytes, NULL, GL_STATIC_DRAW );

        void* pImage = glMapBuffer( GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY );
        if( pImage )
        {
            memcpy( pImage, pData, iSizeInBytes );
            glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER );

            if( m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
                m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
                m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
                m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
            {
                glCompressedTexSubImage2D( m_eType, 
                                           iLevel,
                                           iXOffset, iYOffset, 
                                           iWidth, iHeight,
                                           m_pLevelDescTable[iLevel].m_eInterFormat,
                                           iSizeInBytes,
                                           NULL );
            }
            else
            {
                glTexSubImage2D( m_eType, 
                                 iLevel, 
                                 iXOffset, iYOffset, 
                                 iWidth, iHeight, 
                                 _getGLFormat(enumDataFormat), _getGLType(enumDataFormat), 
                                 NULL );
            }

            result = E_SUCCESS;
        }

        glBindTexture( m_eType, 0 );
        glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0 );
    }

    assert(result == E_SUCCESS);
    return result;
}

ERESULT 
CTexture2DObject::getData( int iLevel, int iXOffset, int iYOffset, int iWidth, int iHeight, EDataFormat enumDataFormat, void* pData )
{
    #define BUFFER_OFFSET(i) ((char*)NULL + (i))
    THREAD_CHECKING(m_pDeviceRef->getCurThreadID(), E_THREAD_ERROR);

    int iSizeInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iSizeInBytes = ((iWidth+3)/4) * ((iHeight+3)/4) * _sizeof(enumDataFormat);
    else
        iSizeInBytes = iWidth * iHeight * _sizeof(enumDataFormat);

    int iOffsetInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iOffsetInBytes = ((iXOffset+3)/4) * ((iYOffset+3)/4) * _sizeof(enumDataFormat);
    else
        iOffsetInBytes = iXOffset * iYOffset * _sizeof(enumDataFormat);

    ERESULT result = E_INVALID_OPERATION;
    if( iSizeInBytes != 0 && iLevel >= 0 && iLevel < m_iNumOfMipmap )
    {
        glBindBuffer( GL_PIXEL_PACK_BUFFER, m_uiBufID );
        glBindTexture( m_eType, m_uiObjID );
        
        glBufferData( GL_PIXEL_PACK_BUFFER, iSizeInBytes, NULL, GL_STATIC_DRAW );

        if( m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
            m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
            m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
            m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
        {
            glGetCompressedTexImage( m_eType, iLevel, BUFFER_OFFSET(iOffsetInBytes) );
        }
        else
        {
            glGetTexImage( m_eType, iLevel, _getGLFormat(enumDataFormat), _getGLType(enumDataFormat), BUFFER_OFFSET(iOffsetInBytes) );
        }

        void* pImage = glMapBuffer( GL_PIXEL_PACK_BUFFER, GL_READ_ONLY );
        if( pImage )
        {
            memcpy( pData, pImage, iSizeInBytes );
            glUnmapBuffer( GL_PIXEL_PACK_BUFFER );
            result = E_SUCCESS;
        }

        glBindTexture( m_eType, 0 );
        glBindBuffer( GL_PIXEL_PACK_BUFFER, 0 );
    }

    assert(result == E_SUCCESS);
    return result;
}

//----------------------------
// class CTexture3DObject
//----------------------------
CTexture3DObject::CTexture3DObject( CRenderDevice* pDevice, STexture3DDesc& Desc ) :
CTextureBaseObject(pDevice, E_TEXTURE_3D)
{
    glBindTexture( m_eType, m_uiObjID );

    int iWidth = Desc.m_iWidth;
    int iHeight = Desc.m_iHeight;
    int iDepth = Desc.m_iDepth;
    m_iNumOfMipmap = Desc.m_iNumMipmap;
    m_pLevelDescTable = new STexture3DLevelDesc[m_iNumOfMipmap];

    for( int i=0; i<m_iNumOfMipmap; i++ )
    {
        if( iWidth == 0 ) iWidth = 1;
        if( iHeight == 0 ) iHeight = 1;
        if( iDepth == 0 ) iDepth = 1;

        m_pLevelDescTable[i].m_eInterFormat = Desc.m_eInterFormat;
        m_pLevelDescTable[i].m_iWidth = iWidth;
        m_pLevelDescTable[i].m_iHeight = iHeight;
        m_pLevelDescTable[i].m_iDepth = iDepth;
        m_pLevelDescTable[i].m_iBorder = Desc.m_iBorder;

        if( m_pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
            m_pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
            m_pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
            m_pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
        {
            glCompressedTexImage3D( m_eType, 
                                    i, 
                                    m_pLevelDescTable[i].m_eInterFormat,
                                    m_pLevelDescTable[i].m_iWidth, m_pLevelDescTable[i].m_iHeight, m_pLevelDescTable[i].m_iDepth, m_pLevelDescTable[i].m_iBorder,
                                    0,
                                    NULL );
        }
        else
        {
            glTexImage3D( m_eType, 
                          i, 
                          m_pLevelDescTable[i].m_eInterFormat, 
                          m_pLevelDescTable[i].m_iWidth, m_pLevelDescTable[i].m_iHeight, m_pLevelDescTable[i].m_iDepth, m_pLevelDescTable[i].m_iBorder,
                          _getGLFormat(E_DATAFORMAT_RGBA_BYTE), _getGLType(E_DATAFORMAT_RGBA_BYTE), 
                          NULL );
        }

        iWidth /= 2;
        iHeight /= 2;
        iDepth /= 2;
    }

    glBindTexture( m_eType, 0 );
}

CTexture3DObject::~CTexture3DObject( void )
{
    SAFEDELETE_ARRAY( m_pLevelDescTable );
}

ERESULT 
CTexture3DObject::getLevelDesc( int iLevel, STexture3DLevelDesc& Desc )
{
    if( (iLevel < 0 && iLevel >= m_iNumOfMipmap) )
        return E_INVALID_VALUE;
    else
    {
        Desc = m_pLevelDescTable[iLevel];
        return E_SUCCESS;
    }
}

ERESULT 
CTexture3DObject::setData( int iLevel, int iXOffset, int iYOffset, int iZOffset, int iWidth, int iHeight, int iDepth, EDataFormat enumDataFormat, void* pData )
{
    THREAD_CHECKING(m_pDeviceRef->getCurThreadID(), E_THREAD_ERROR);

    ERESULT result = E_INVALID_OPERATION;
    int iSizeInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iSizeInBytes = ((iWidth+3)/4) * ((iHeight+3)/4) * iDepth * _sizeof(enumDataFormat);
    else
        iSizeInBytes = iWidth * iHeight * iDepth * _sizeof(enumDataFormat);

    if( iSizeInBytes != 0 && iLevel >= 0 && iLevel < m_iNumOfMipmap )
    {
        glBindBuffer( GL_PIXEL_UNPACK_BUFFER, m_uiBufID );
        glBindTexture( m_eType, m_uiObjID );
        glBufferData( GL_PIXEL_UNPACK_BUFFER, iSizeInBytes, NULL, GL_STATIC_DRAW );

        void* pImage = glMapBuffer( GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY );
        if( pImage )
        {
            memcpy( pImage, pData, iSizeInBytes );
            glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER );

            if( m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
                m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
                m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
                m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
            {
                glCompressedTexSubImage3D( m_eType, 
                                           iLevel,
                                           iXOffset, iYOffset, iZOffset,
                                           iWidth, iHeight, iDepth,
                                           m_pLevelDescTable[iLevel].m_eInterFormat,
                                           iSizeInBytes,
                                           NULL );
            }
            else
            {
                glTexSubImage3D( m_eType, 
                                 iLevel, 
                                 iXOffset, iYOffset, iZOffset,
                                 iWidth, iHeight, iDepth,
                                 _getGLFormat(enumDataFormat), _getGLType(enumDataFormat), 
                                 NULL );
            }

            result = E_SUCCESS;
        }

        glBindTexture( m_eType, 0 );
        glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0 );
    }

    assert(result == E_SUCCESS);
    return result;
}

ERESULT 
CTexture3DObject::getData( int iLevel, int iXOffset, int iYOffset, int iZOffset, int iWidth, int iHeight, int iDepth, EDataFormat enumDataFormat, void* pData )
{
    #define BUFFER_OFFSET(i) ((char*)NULL + (i))
    THREAD_CHECKING(m_pDeviceRef->getCurThreadID(), E_THREAD_ERROR);

    int iSizeInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iSizeInBytes = ((iWidth+3)/4) * ((iHeight+3)/4) * iDepth * _sizeof(enumDataFormat);
    else
        iSizeInBytes = iWidth * iHeight * iDepth * _sizeof(enumDataFormat);

    int iOffsetInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iOffsetInBytes = ((iXOffset+3)/4) * ((iYOffset+3)/4) * iZOffset * _sizeof(enumDataFormat);
    else
        iOffsetInBytes = iXOffset * iYOffset * iZOffset * _sizeof(enumDataFormat);

    ERESULT result = E_INVALID_OPERATION;
    if( iSizeInBytes != 0 && iLevel >= 0 && iLevel < m_iNumOfMipmap )
    {
        glBindBuffer( GL_PIXEL_PACK_BUFFER, m_uiBufID );
        glBindTexture( m_eType, m_uiObjID );
        
        glBufferData( GL_PIXEL_PACK_BUFFER, iSizeInBytes, NULL, GL_STATIC_DRAW );

        if( m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
            m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
            m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
            m_pLevelDescTable[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
        {
            glGetCompressedTexImage( m_eType, iLevel, BUFFER_OFFSET(iOffsetInBytes) );
        }
        else
        {
            glGetTexImage( m_eType, iLevel, _getGLFormat(enumDataFormat), _getGLType(enumDataFormat), BUFFER_OFFSET(iOffsetInBytes) );
        }

        void* pImage = glMapBuffer( GL_PIXEL_PACK_BUFFER, GL_READ_ONLY );
        if( pImage )
        {
            memcpy( pData, pImage, iSizeInBytes );
            glUnmapBuffer( GL_PIXEL_PACK_BUFFER );
            result = E_SUCCESS;
        }

        glBindTexture( m_eType, 0 );
        glBindBuffer( GL_PIXEL_PACK_BUFFER, 0 );
    }

    assert(result == E_SUCCESS);
    return result;
}

//------------------------------
// class CTextureCubeObject
//------------------------------
CTextureCubeObject::CTextureCubeObject( CRenderDevice* pDevice, STextureCubeDesc& Desc ) :
CTextureBaseObject(pDevice, E_TEXTURE_CUBE_MAP)
{
    glBindTexture( m_eType, m_uiObjID );

    ETexCubeFace faceTable[6] = { E_POSITIVE_X, E_NEGATIVE_X, E_POSITIVE_Y, E_NEGATIVE_Y, E_POSITIVE_Z, E_NEGATIVE_Z };
    for( int f=0; f<6; f++ )
    {
        int iWidth = Desc.m_iWidth;
        int iHeight = Desc.m_iHeight;
        m_iNumOfMipmap = Desc.m_iNumMipmap;
        STextureCubeLevelDesc* pLevelDescTable = new STextureCubeLevelDesc[m_iNumOfMipmap];

        for( int i=0; i<m_iNumOfMipmap; i++ )
        {
            if( iWidth == 0 ) iWidth = 1;
            if( iHeight == 0 ) iHeight = 1;

            pLevelDescTable[i].m_eInterFormat = Desc.m_eInterFormat;
            pLevelDescTable[i].m_iWidth = iWidth;
            pLevelDescTable[i].m_iHeight = iHeight;
            pLevelDescTable[i].m_iBorder = Desc.m_iBorder;

            if( pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
                pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
                pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
                pLevelDescTable[i].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
            {
                glCompressedTexImage2D( m_eType, 
                                        i, 
                                        pLevelDescTable[i].m_eInterFormat,
                                        pLevelDescTable[i].m_iWidth, pLevelDescTable[i].m_iHeight, pLevelDescTable[i].m_iBorder,
                                        0,
                                        NULL );
            }
            else
            {
                glTexImage2D( m_eType, 
                              i, 
                              pLevelDescTable[i].m_eInterFormat, 
                              pLevelDescTable[i].m_iWidth, pLevelDescTable[i].m_iHeight, pLevelDescTable[i].m_iBorder,
                              _getGLFormat(E_DATAFORMAT_RGBA_BYTE), _getGLType(E_DATAFORMAT_RGBA_BYTE), 
                              NULL );
            }

            iWidth /= 2;
            iHeight /= 2;
        }

        m_ppLevelDescTable[f] = (&pLevelDescTable);
    }

    glBindTexture( m_eType, 0 );
}

CTextureCubeObject::~CTextureCubeObject( void )
{
    for( int f=0; f<6; f++ )
    {
        SAFEDELETE_ARRAY( (*m_ppLevelDescTable[f]) );
    }
}

int 
CTextureCubeObject::_getIndexOfFace( ETexCubeFace enumFace )
{
    switch( enumFace )
    {
    case E_POSITIVE_X:
        return 0;
    case E_NEGATIVE_X:
        return 1;
    case E_POSITIVE_Y:
        return 2;
    case E_NEGATIVE_Y:
        return 3;
    case E_POSITIVE_Z:
        return 4;
    default:
        return 5;
    }
}

ERESULT 
CTextureCubeObject::getLevelDesc( ETexCubeFace enumFace, int iLevel, STextureCubeLevelDesc& Desc )
{
    if( (iLevel < 0 && iLevel >= m_iNumOfMipmap) )
        return E_INVALID_VALUE;
    else
    {
        int iFace = _getIndexOfFace( enumFace );
        Desc = (*m_ppLevelDescTable[iFace])[iLevel];
        return E_SUCCESS;
    }
}

ERESULT 
CTextureCubeObject::setData( ETexCubeFace enumFace, int iLevel, int iXOffset, int iYOffset, int iWidth, int iHeight, EDataFormat enumDataFormat, void* pData )
{
    THREAD_CHECKING(m_pDeviceRef->getCurThreadID(), E_THREAD_ERROR);

    ERESULT result = E_INVALID_OPERATION;
    int iSizeInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iSizeInBytes = ((iWidth+3)/4) * ((iHeight+3)/4) * _sizeof(enumDataFormat);
    else
        iSizeInBytes = iWidth * iHeight * _sizeof(enumDataFormat);

    if( iSizeInBytes != 0 && iLevel >= 0 && iLevel < m_iNumOfMipmap )
    {
        glBindBuffer( GL_PIXEL_UNPACK_BUFFER, m_uiBufID );
        glBindTexture( m_eType, m_uiObjID );
        glBufferData( GL_PIXEL_UNPACK_BUFFER, iSizeInBytes, NULL, GL_STATIC_DRAW );

        void* pImage = glMapBuffer( GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY );
        if( pImage )
        {
            memcpy( pImage, pData, iSizeInBytes );
            glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER );

            int iFace = _getIndexOfFace( enumFace );
            if( (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
                (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
                (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
                (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
            {
                glCompressedTexSubImage2D( enumFace, 
                                           iLevel,
                                           iXOffset, iYOffset, 
                                           iWidth, iHeight,
                                           (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat,
                                           iSizeInBytes,
                                           NULL );
            }
            else
            {
                glTexSubImage2D( enumFace, 
                                 iLevel, 
                                 iXOffset, iYOffset,
                                 iWidth, iHeight, 
                                 _getGLFormat(enumDataFormat), _getGLType(enumDataFormat), 
                                 NULL );
            }

            result = E_SUCCESS;
        }

        glBindTexture( m_eType, 0 );
        glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0 );
    }

    assert(result == E_SUCCESS);
    return result;
}

ERESULT 
CTextureCubeObject::getData( ETexCubeFace enumFace, int iLevel, int iXOffset, int iYOffset, int iWidth, int iHeight, EDataFormat enumDataFormat, void* pData )
{
    #define BUFFER_OFFSET(i) ((char*)NULL + (i))
    THREAD_CHECKING(m_pDeviceRef->getCurThreadID(), E_THREAD_ERROR);

    int iSizeInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iSizeInBytes = ((iWidth+3)/4) * ((iHeight+3)/4) * _sizeof(enumDataFormat);
    else
        iSizeInBytes = iWidth * iHeight * _sizeof(enumDataFormat);

    int iOffsetInBytes = 0;
    if( enumDataFormat >= E_DATAFORMAT_RGB_DXT1 && enumDataFormat <= E_DATAFORMAT_RGBA_DXT5 )
        iOffsetInBytes = ((iXOffset+3)/4) * ((iYOffset+3)/4) * _sizeof(enumDataFormat);
    else
        iOffsetInBytes = iXOffset * iYOffset * _sizeof(enumDataFormat);

    ERESULT result = E_INVALID_OPERATION;
    if( iSizeInBytes != 0 && iLevel >= 0 && iLevel < m_iNumOfMipmap )
    {
        glBindBuffer( GL_PIXEL_PACK_BUFFER, m_uiBufID );
        glBindTexture( m_eType, m_uiObjID );
        
        glBufferData( GL_PIXEL_PACK_BUFFER, iSizeInBytes, NULL, GL_STATIC_DRAW );

        int iFace = _getIndexOfFace( enumFace );
        if( (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat == E_INTERFORMAT_RGB_DXT1  ||
            (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT1 ||
            (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT3 ||
            (*m_ppLevelDescTable[iFace])[iLevel].m_eInterFormat == E_INTERFORMAT_RGBA_DXT5 )
        {
            glGetCompressedTexImage( enumFace, iLevel, BUFFER_OFFSET(iOffsetInBytes) );
        }
        else
        {
            glGetTexImage( enumFace, iLevel, _getGLFormat(enumDataFormat), _getGLType(enumDataFormat), BUFFER_OFFSET(iOffsetInBytes) );
        }

        void* pImage = glMapBuffer( GL_PIXEL_PACK_BUFFER, GL_READ_ONLY );
        if( pImage )
        {
            memcpy( pData, pImage, iSizeInBytes );
            glUnmapBuffer( GL_PIXEL_PACK_BUFFER );
            result = E_SUCCESS;
        }

        glBindTexture( m_eType, 0 );
        glBindBuffer( GL_PIXEL_PACK_BUFFER, 0 );
    }

    assert(result == E_SUCCESS);
    return result;
}

}//End namespace GL

}//End namespace Porman